function long2str(v, w) {
  var vl = v.length;
  var sl = v[vl - 1] & 0xffffffff;
  for (var i = 0; i < vl; i++) {
    v[i] = String.fromCharCode(v[i] & 0xff,
      v[i] >>> 8 & 0xff,
      v[i] >>> 16 & 0xff,
      v[i] >>> 24 & 0xff);
  }
  if (w) {
    return v.join('').substring(0, sl);
  }
  else {
    return v.join('');
  }
}

function str2long(s, w) {
  var len = s.length;
  var v = [];
  for (var i = 0; i < len; i += 4) {
    v[i >> 2] = s.charCodeAt(i)
      | s.charCodeAt(i + 1) << 8
      | s.charCodeAt(i + 2) << 16
      | s.charCodeAt(i + 3) << 24;
  }
  if (w) {
    v[v.length] = len;
  }
  return v;
}

function xxtea_encrypt(str, key) {
  if (str == "") {
    return "";
  }
  var v = str2long(str, true);
  var k = str2long(key, false);
  var n = v.length - 1;

  var z = v[n], y = v[0], delta = 0x9E3779B9;
  var mx, e, q = Math.floor(6 + 52 / (n + 1)), sum = 0;
  while (q-- > 0) {
    sum = sum + delta & 0xffffffff;
    e = sum >>> 2 & 3;
    for (var p = 0; p < n; p++) {
      y = v[p + 1];
      mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
      z = v[p] = v[p] + mx & 0xffffffff;
    }
    y = v[0];
    mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
    z = v[n] = v[n] + mx & 0xffffffff;
  }

  return str2Hex(long2str(v, false));
}

function xxtea_decrypt(str, key) {
  if (str == "") {
    return "";
  }

  str = hex2str(str);
  var v = str2long(str, false);
  var k = str2long(key, false);
  var n = v.length - 1;

  var z = v[n - 1], y = v[0], delta = 0x9E3779B9;
  var mx, e, q = Math.floor(6 + 52 / (n + 1)), sum = q * delta & 0xffffffff;
  while (sum != 0) {
    e = sum >>> 2 & 3;
    for (var p = n; p > 0; p--) {
      z = v[p - 1];
      mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
      y = v[p] = v[p] - mx & 0xffffffff;
    }
    z = v[n];
    mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
    y = v[0] = v[0] - mx & 0xffffffff;
    sum = sum - delta & 0xffffffff;
  }

  return long2str(v, true);
}

function str2Hex(input) {
  var output = "";
  var chr1 = "";
  var i = 0;
  do {
    chr1 = input.charCodeAt(i++).toString(16);
    if (chr1.length == 1) chr1 = "0" + chr1;
    output += chr1;
  } while (i < input.length);
  return output;
}

function hex2str(input) {
  var output = "";
  var i = 0;
  while (i < input.length) {
    var k = parseInt(input.substr(i, 1), 16) << 4 | parseInt(input.substr(++i, 1), 16);
    k = k & 255;
    output += String.fromCharCode(k);
    ++i;
  }
  return output;
}

function utf16to8(str) {
  　　var out, i, len, c;
  　　out = "";
  　　len = str.length;
  　　for (i = 0; i < len; i++) {
    c = str.charCodeAt(i);
    if ((c >= 0x0001) && (c <= 0x007F)) {
      out += str.charAt(i);
    } else if (c > 0x07FF) {
      out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
      out += String.fromCharCode(0x80 | ((c >> 　6) & 0x3F));
      out += String.fromCharCode(0x80 | ((c >> 　0) & 0x3F));
    } else {
      out += String.fromCharCode(0xC0 | ((c >> 　6) & 0x1F));
      out += String.fromCharCode(0x80 | ((c >> 　0) & 0x3F));
    }
  　　}
  　　return out;
}
function utf8to16(str) {
  　　var out, i, len, c;
  　　var char2, char3;
  　　out = "";
  　　len = str.length;
  　　i = 0;
  　　while (i < len) {
    c = str.charCodeAt(i++);
    switch (c >> 4) {
      case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
        // 0xxxxxxx
        out += str.charAt(i - 1);
        break;
      case 12: case 13:
        // 110x xxxx　 10xx xxxx
        char2 = str.charCodeAt(i++);
        out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
        break;
      case 14:
        // 1110 xxxx　10xx xxxx　10xx xxxx
        char2 = str.charCodeAt(i++);
        char3 = str.charCodeAt(i++);
        out += String.fromCharCode(((c & 0x0F) << 12) |
          　　　　((char2 & 0x3F) << 6) |
          　　　　((char3 & 0x3F) << 0));
        break;
    }
  　　}
  　　return out;
}

module.exports = {
  xxtea_encrypt: xxtea_encrypt,
  xxtea_decrypt: xxtea_decrypt,
  utf16to8: utf16to8,
  utf8to16: utf8to16,
}